/*
 *
 */
package org.jiscinvolve.astrodabis.barberry.hibernate.core;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;

import java.util.List;
import java.util.Iterator;

import org.hibernate.Query;
import org.hibernate.Session;
import org.hibernate.CacheMode;
import org.hibernate.Transaction;
import org.hibernate.SessionFactory;
import org.hibernate.StatelessSession;
import org.hibernate.HibernateException;

import org.hibernate.ScrollMode;
import org.hibernate.ScrollableResults;

import org.springframework.stereotype.Repository;
import org.springframework.dao.DataAccessException;
import org.springframework.orm.hibernate3.HibernateTemplate; 
import org.springframework.beans.factory.annotation.Autowired;

import org.jiscinvolve.astrodabis.barberry.common.core.Womble;
import org.jiscinvolve.astrodabis.barberry.common.core.Identifier;

/**
 *
 */
@Repository
public abstract class AbstractFactory<InterfaceType, ObjectType extends InterfaceType>
    {

    /**
     * Our debug logger.
     * 
     */
    private static Log log = LogFactory.getLog(AbstractFactory.class);

    /**
     * Our autowired reference to the global Womble.
     *
     */
    @Autowired
    private Womble womble ;
    public Womble womble()
        {
        return womble;
        }

    /**
     * Our autowired HibernateTemplate.
     *
     */
    @Autowired
    private HibernateTemplate template;
    protected HibernateTemplate template()
        {
        return template;
        }

    /**
     * Our autowired Hibernate SessionFactory.
     *
     */
    @Autowired
    private SessionFactory hibernate;

    /**
     * Inner class to wrap common Hibernate functions.
     *
     */
    public class HibernateTools<ObjectType>
        {
        /**
         * Wrap a Hibernate Exception.
         *
         */
        public DataAccessException convert(HibernateException ouch)
            {
            log.error("Error executing Hibernate query [" + ouch.getClass().getName() + "][" + ouch.getMessage() + "]");
            return template.convertHibernateAccessException(
                ouch
                );
            }

        /**
         * Our current Hibernate Session.
         *
         */
        private Session session()
            {
            return hibernate.getCurrentSession();
            }

/*
 * Problems with StatelessSessionImpl
 * https://forum.hibernate.org/viewtopic.php?f=1&t=928883&view=previous
 * https://forum.hibernate.org/viewtopic.php?f=1&t=990632&view=previous
 * http://docs.jboss.org/hibernate/core/3.3/reference/en/html/batch.html#batch-statelesssession
 *
 * Fix is to set the flush and cache modes to null.
 *
 
//
// StatelessSessionImpl contains these .. 
public class StatelessSessionImpl
    {
    public void setCacheMode(CacheMode cm)
        {
        throw new UnsupportedOperationException();
        }

    public void setFlushMode(FlushMode fm)
        {
        throw new UnsupportedOperationException();
        }
    }

//
// Which are called by .. 
public class AbstractQueryImpl
    {
    protected void before()
        {
        if ( flushMode!=null )
            {
            sessionFlushMode = getSession().getFlushMode();
            getSession().setFlushMode(flushMode);
            }
        if ( cacheMode!=null )
            {
            sessionCacheMode = getSession().getCacheMode();
            getSession().setCacheMode(cacheMode);
            }
        }

    protected void after()
        {
        if (sessionFlushMode!=null)
            {
            getSession().setFlushMode(sessionFlushMode);
            sessionFlushMode = null;
            }
        if (sessionCacheMode!=null)
            {
            getSession().setCacheMode(sessionCacheMode);
            sessionCacheMode = null;
            }
        }
    }

 */

        /**
         * Get a named query.
         *
         */
        public Query query(String name)
            {
            //log.debug("HibernateTools.query(String)");
            try {
                if (stateless() != null)
                    {
                    //log.debug("using stateLESS session to fetch named query");
                    Query query = stateless().session().getNamedQuery(
                        name
                        );
                    if (query == null)
                        {
                        log.error("query is null");
                        }
                    else {
                        query.setCacheMode(null);
                        query.setFlushMode(null);
                        }                        
                    return query ;
                    }
                else {
                    //log.debug("using stateFULL session");
                    return this.session().getNamedQuery(
                        name
                        );
                    }
                }
            catch (HibernateException ouch)
                {
                throw convert(
                    ouch
                    );
                }
            }

        /**
         * Insert a new persistent object.
         *
         */
        public ObjectType insert(ObjectType object)
            {
            //log.debug("HibernateTools.insert(ObjectType)");
            try {
                if (stateless() != null)
                    {
                    //log.debug("using stateLESS session");
                    stateless().session().insert(
                        object
                        );
                    }
                else {
                    //log.debug("using stateFULL session");
                    this.session().save(
                        object
                        );
                    this.flush();
                    }
                return object ;
                }
            catch (HibernateException ouch)
                {
                throw convert(
                    ouch
                    );
                }
            }

        /**
         * Select a specific object.
         *
         */
        public ObjectType select(Class type, Identifier ident)
            {
            //log.debug("HibernateTools.select(Class, Identifier)");
            try {
                if (ident == null)
                    {
                    return null ;
                    }
                else {
                    if (stateless() != null)
                        {
                        //log.debug("using stateLESS session");
                        return (ObjectType) stateless().session().get(
                            type,
                            ident.value()
                            );
                        }
                    else {
                        //log.debug("using stateFULL session");
                        return (ObjectType) this.session().get(
                            type,
                            ident.value()
                            );
                        }
                    }
                }
            catch (HibernateException ouch)
                {
                throw convert(
                    ouch
                    );
                }
            }

        /**
         * Select a single object.
         *
         */
        public InterfaceType single(Query query)
            {
/*
 * Turns out uniqueResult() uses list() anyway.
            try {
                return (InterfaceType) query.uniqueResult();
                }
            catch (HibernateException ouch)
                {
                throw hibernate().convert(
                    ouch
                    );
                }
 *
 */
            return (InterfaceType) first(query);
            }

        /**
         * Return the first result of a query.
         *
         */
        public ObjectType first(Query query)
            {
            try {
/*
 * Using Iterator causes a LazyInitializationException.
 * org.hibernate.LazyInitializationException: could not initialize proxy - no Session
 *     at org.hibernate.proxy.AbstractLazyInitializer.initialize(AbstractLazyInitializer.java:167)
 *     at org.hibernate.proxy.AbstractLazyInitializer.getImplementation(AbstractLazyInitializer.java:215)
 *     at org.hibernate.proxy.pojo.javassist.JavassistLazyInitializer.invoke(JavassistLazyInitializer.java:190)
 *     at org.jiscinvolve.astrodabis.barberry.hibernate.data.TapColumnEntity_$$_javassist_8.ident(TapColumnEntity_$$_javassist_8.java)
 *     at org.jiscinvolve.astrodabis.barberry.common.data.TapColumnTestCase.checkCreatedTwiceReturnsSame(TapColumnTestCase.java:89)
 * 

                Iterator<ObjectType> iter = query.iterate();
                if (iter.hasNext())
                    {
                    return iter.next();
                    }
                else {
                    return null ;
                    }
 */
/*
 * This works ..
                List<ObjectType> list = query.list();
                if (list.isEmpty())
                    {
                    return null ;
                    }
                else {
                    return list.get(0);
                    }
 */

                ScrollableResults results = query.scroll(ScrollMode.FORWARD_ONLY);
                if (results.next())
                    {
                    return (ObjectType) results.get(0);
                    }
                else {
                    return null ;
                    }
                }
            catch (HibernateException ouch)
                {
                throw convert(
                    ouch
                    );
                }
            }

        /**
         * Select an Iterable set of objects.
         *
         */
        public Iterable<InterfaceType> iterable(final Query query)
            {
            return new Iterable<InterfaceType>()
                {
                public Iterator<InterfaceType> iterator()
                    {
                    try {
                        return (Iterator<InterfaceType>) query.iterate();
                        }
                    catch (HibernateException ouch)
                        {
                        throw convert(
                            ouch
                            );
                        }
                    }
                };
            }

        /**
         * Update an object.
         *
         */
        public void update(InterfaceType object)
            {
            template.update(
                object
                );
            }

        /**
         * Delete an object.
         *
         */
        public void delete(InterfaceType object)
            {
            template.delete(
                object
                );
            }

        /**
         * Flush changes to the database.
         *
         */
        protected void flush()
            {
            template.flush();
            }

        /**
         * Clear the session state, discarding unsaved changes.
         *
         */
        protected void clear()
            {
            template.clear();
            }

        }

    public HibernateTools<ObjectType> hibernate()
        {
        return new HibernateTools<ObjectType>();
        }

    public Womble.StatelessSessionTools stateless()
        {
        return womble.stateless();
        }

    public void startless()
        {
        womble.startless();
        }

/*
 * Flush changes ..
 *
 * Clear session ..
 *
 * Evict objects from cache ..
 * http://www.basilv.com/psd/blog/2010/avoiding-caching-to-improve-hibernate-performance
 *
 * Use StatelessSession
 * http://docs.jboss.org/hibernate/core/3.6/reference/en-US/html/batch.html#batch-statelesssession
 * https://www.owasp.org/index.php/Hibernate-Guidelines#About_StatelessSession
 *
 *
 *
 */
    }


